class DynamicArray:

    def __init__(self):
        self._size = 0
        self._capacity = 1
        self._array = [None] * self._capacity

    def __len__(self):
        return self._size
    #special methods for indexing - get
    def __getitem__(self, index):
        if 0 <= index < self._size:
            return self._array[index]
        else:
            raise IndexError("DynamicArray index out of range")
    #special methods for indexing - set
    def __setitem__(self, index, value):
        if 0 <= index < self._size:
            self._array[index] = value
        else:
            raise IndexError("DynamicArray index out of range")
    
    def append(self, el):
        if self._size == self._capacity:
            self._resize()
        self._array[self._size] = el
        self._size += 1

    # The only one more function that a dynamic has over a staticArray
    def _resize(self):
        print("Resizing from", self._capacity, "to", {self._capacity * 2})
        self._capacity *= 2
        new_array = [None] * self._capacity

        for i in range(self._size):
            new_array[i] = self._array[i]

        self._array = new_array
    
    def pop(self):
        if self._size == 0:
            raise IndexError("pop from empty DynamicArray")
        last_el = self._array[self._size - 1]
        self._array[self._size - 1] = None
        self._size -= 1

        return last_el
    
    def insert(self, index, value):
        if index < 0 or index > self._size:
            raise IndexError("DynamicArray index out of range")
        
        if self._size == self._capacity:
            self._resize()
        
        for i in range(self._size, index, -1):
            self._array[i] = self._array[i - 1]
        
        self._array[index] = value
        self._size += 1
    
    def remove(self, value):
        for i in range(self._size):
            if self._array[i] == value:
                self.remove_at(i)
                return
        raise ValueError("DynamicArray.remove(x): x not in array")

    def remove_at(self, index):
        if index < 0 or index >= self._size:
            raise IndexError("DynamicArray index out of range")
        
        for i in range(index, self._size - 1):
            self._array[i] = self._array[i + 1]
        
        self._array[self._size - 1] = None
        self._size -= 1
    
    def __str__(self):
        elements = [str(self._array[i]) for i in range(self._size)]
        return f"[{', '.join(elements)}] (capacity: {self._capacity})"
    
    def __repr__(self):
        return self.__str__()

if __name__ == "__main__":
    arr = DynamicArray()
    for i in range(10):
        arr.append(i)
        print(arr)
    
    for _ in range(5):
        arr.pop()
        print(arr)
    
    arr.insert(2, 99)
    print(arr)
    
    arr.remove(99)
    print(arr)